<?php

///////////////////////////////////////////////////////////////////////////////////////////
// PROPRIETARY INFORMATION CONTAINED HEREIN.
// 
// version 8
//
//	HISTORY
//		21Oct2005 Removed force case on XML tags.
//		12Jun2006 Added HTTP_AUTH to send_request()
//		05May2007 Added error handling of malformed XML to parse() method.
//		29Oct2007 Added lowercase_tags() method for backward compatability (unserialize).
//
// $request = array(
//	"REQUEST" => array(
//		"UPDATE_STATUS" => array(
//			"ORDERID" => 100,
//			"STATUS" => "Shipped",
//			)
//		)
// 	);
// $XML_request = XML_serialize($request);
// $XML_reply = send_request($url, $XML_request);
// $reply = XML_unserialize($XML_reply, false);
// define('XML_DEBUG', true);

class XML
{
	var $parser;
	var $document;
	var $current;
	var $parent;
	var $parents;
	var $error_code;
	var $error_line;
	var $error_column;
	var $error_string;
	var $tolower;
    var $force_node_enum = false;
	var $last_opened_tag;

	function XML($data=NULL)
	{
		$this->tolower = true;
		$this->parser = xml_parser_create("UTF-8");

		xml_parser_set_option ($this->parser, XML_OPTION_CASE_FOLDING, 0);
		xml_parser_set_option ($this->parser, XML_OPTION_TARGET_ENCODING, "UTF-8");
		xml_set_object($this->parser, $this);
		xml_set_element_handler($this->parser, "open", "close");
		xml_set_character_data_handler($this->parser, "data");
#		register_shutdown_function(array(&$this, 'destruct'));
	}

	function destruct(){
		xml_parser_free($this->parser);
	}
	function remove_blank_lines(&$data){
        return preg_replace("/^[ \t]*[\r\n]+/m","",$data);
	}
	function parse($data,$force_node_enum=False){
		//require_once(constant('SHARE_DOCUMENT_ROOT').'/lib.util.php');	
		$data = $this->remove_blank_lines($data);
		$this->force_node_enum = $force_node_enum;
		$this->document = array();
		$this->parent = &$this->document;
		$this->parents = array();
		$this->last_opened_tag = NULL;
		$this->error_code	= false;
		$this->error_line	= false;
		$this->error_column	= false;
		$this->error_string	= false;
		if (!xml_parse($this->parser, $data, true))
		{
			$this->error_code	= xml_get_error_code($this->parser);
			$this->error_line	= xml_get_current_line_number($this->parser);
			$this->error_column	= xml_get_current_column_number($this->parser);
			$this->error_string	= xml_error_string($this->error_code);
			return false;
		}
		return $this->document;
	}
    function _is_tag_need_to_force_enum($tag,$case_sensitive=FALSE){
        if(is_array($this->force_node_enum)){
            if($case_sensitive){
                return array_search($tag, $this->force_node_enum) !== FALSE;
            }else{
                foreach($this->force_node_enum as $key){
                    if(strtolower($key) == strtolower($tag)){
                       return TRUE;
                    }
                }
            }
            return FALSE;
        }
        return $this->force_node_enum === TRUE;
    }
	function open($parser, $tag, $attributes)
	{
		if (defined('XML_DEBUG') && constant('XML_DEBUG'))
			echo "open($tag)<br>\n";
		if ($this->tolower)
			$tag = strtolower($tag);
		$this->data = "";
		$this->last_opened_tag = $tag; #tag is a string
		if(array_key_exists($tag, $this->parent))
		{
			if (defined('XML_DEBUG') && constant('XML_DEBUG'))
				echo "There's already an instance of '$tag' at the current level ($level)<br>\n";
            $key_index = 0;
            if($this->_is_tag_need_to_force_enum($tag)){
                $key_index =1;
            }
			if(is_array($this->parent[$tag]) && array_key_exists($key_index, $this->parent[$tag]))
			{ # if the keys are numeric
				# need to make sure they're numeric (account for attributes)
				$key = $this->count_numeric_items($this->parent[$tag]);
				if (defined('XML_DEBUG') && constant('XML_DEBUG'))
					echo "There are $key instances: the keys are numeric.<br>\n";
			}
			else
			{
				if (defined('XML_DEBUG') && constant('XML_DEBUG'))
					echo "There is only one instance. Shifting everything around<br>\n";
				$temp = &$this->parent[$tag];
				unset($this->parent[$tag]);
                if($this->_is_tag_need_to_force_enum($tag)){
                    $this->parent[$tag][0] = &$temp[0];
                }else{
                    $this->parent[$tag][0] = &$temp;
                }
				if(array_key_exists("$tag attr", $this->parent))
				{
					#shift the attributes around too if they exist
					$temp = &$this->parent["$tag attr"];
					unset($this->parent["$tag attr"]);
					$this->parent[$tag]["0 attr"] = &$temp;
				}
				$key = 1;

			}
			$this->parent = &$this->parent[$tag];
		}else{
			$key = $tag;
		}
		if($attributes){
			$this->parent["$key attr"] = $attributes;
		}
        if($this->_is_tag_need_to_force_enum($tag) && !is_numeric($key)){
            $this->parent[$key] = array(array());
            $this->parent =& $this->parent[$key][0];
        }else{
            $this->parent[$key] = array();
            $this->parent =& $this->parent[$key];
        }
// correct pass-by-reference fault under PHP 5.
//		array_unshift($this->parents, &$this->parent);
		array_unshift($this->parents, array());
		$this->parents[0] = &$this->parent;
	}

	function data($parser, $data)
	{
		if ($this->last_opened_tag != NULL)
		{
			if (defined('XML_DEBUG') && constant('XML_DEBUG'))
				printf("data(%s)<br>\n", internal_htmlentities($data));
			$this->data .= preg_replace("/\\\\n/", "\n", $data);
		}
	}

	function close($parser, $tag)
	{
		if (defined('XML_DEBUG') && constant('XML_DEBUG'))
			echo "close($tag)<br>\n";
		if ($this->tolower)
			$tag = strtolower($tag);
		if ($this->last_opened_tag == $tag){
            $this->parent = $this->data;
			$this->last_opened_tag = NULL;
		}
		array_shift($this->parents);
		$this->parent = &$this->parents[0];
	}

	function count_numeric_items(&$array)
	{
		return is_array($array) ? count(array_filter(array_keys($array), 'is_numeric')) : 0;
	}

	function get_error()
	{
		return array(
			'code' => $this->error_code,
			'text' => $this->error_string,
			'line' => $this->error_line,
			'column' => $this->error_column
			);
	}

	function get_error_text()
	{
		return sprintf('XML error: (%0d) %s at line: %0d column: %0d.',
			$this->error_code,
			$this->error_string,
			$this->error_line,
			$this->error_column
			);
	}

	function lowercase_tags($boolean=true)
	{
		$this->tolower = $boolean ? true : false;
	}
	
	function XML_node($obj, $path, $force_lowercase_tags=true)
{
	if (false == is_array($obj))
		return false;

	if ($force_lowercase_tags)
	{
		$path = strtolower($path);
		$obj = recurse_lowercase_keys($obj);
	}	
	$path = trim($path, '/');
	if (strlen($path) == 0)
		return false;
		
	$names = split('/', $path);
	$ref = $obj;
	foreach ($names as $key)
	{
		$key = trim($key);
		//check is there property selector likes '/user[@name=kevin]'
		if(preg_match('/(.+)\[@(.+)=(.+)\]$/',$key,$matches))
		{
			if(count($matches) == 4)
			{
				$node_key = $matches[1];
				$property_name = $matches[2];
				$property_value = $matches[3];
				if(is_array($ref) && array_key_exists($node_key, $ref) && is_array($ref[$node_key]))
				{
					$node_list = $ref[$node_key];
					$get_target = false;
					foreach($node_list as $node_list_index=>$tmpObj)
					{
						if(!is_int($node_list_index))
							continue;
						if(array_key_exists($node_list_index.' attr',$node_list) 
						&& is_array($node_list[$node_list_index.' attr'])
						&&array_key_exists($property_name,$node_list[$node_list_index.' attr'])
						)
						{
							$is_property_value_match = false;
							if ($force_lowercase_tags)
							{	
								if(strtolower($node_list[$node_list_index.' attr'][$property_name]) == strtolower($property_value))
								{
									$is_property_value_match = true;
								}
							}else{
								if($node_list[$node_list_index.' attr'][$property_name] == $property_value)
								{
									$is_property_value_match = true;
								}
							}
							if($is_property_value_match)
							{
								$ref = $node_list[$node_list_index];
								$get_target = true;
								break;
							}
							
						}
					}
					if($get_target)
						continue;
				}
			}
		}else{
			if (is_array($ref) && array_key_exists($key, $ref))
			{
				$ref = $ref[$key];
				continue;
			}
		}
		return false;		// key not found
	}
	return $ref;
	} 
	
	public static function XML_serialize($data, $header = true, $level = 0, $prior_key = NULL){
	# assumes a hash, keys are the variable names
	$xml_serialized_string = "";
	if (is_array($data))
	{
		foreach ($data as $key => $value)
		{
			$inline = false;
			$numeric_array = false;
			$attributes = "";
			if (defined('XML_DEBUG') && constant('XML_DEBUG'))
				echo "current key: '$key'; prior key: '$prior_key'; level: $level<br>";

			// if a master key name (not an attribute)
			if (!strstr($key, " attr"))
			{
				// if attributes exist for key
				if (array_key_exists("$key attr", $data))
				{
					while ( list($attr_name, $attr_value) = each($data["$key attr"]))
					{
						if (defined('XML_DEBUG') && constant('XML_DEBUG'))
							echo "Found attribute $attribute_name with value $attribute_value<br>";
						$attr_value = &internal_htmlspecialchars($attr_value, ENT_QUOTES);
						$attributes .= " $attr_name=\"$attr_value\"";
					}
				}

				if (is_numeric($key))
				{
					if (defined('XML_DEBUG') && constant('XML_DEBUG'))
						echo " key is numeric<br>";
					$key = $prior_key;
				}
				else
				{
					if (is_array($value) && array_key_exists(0, $value))
					{
						if (defined('XML_DEBUG') && constant('XML_DEBUG'))
							echo " value looks like numeric array.<br>";
						$numeric_array = true;
						$xml_serialized_string .= XML::XML_serialize($value, 0, $level, $key);
					}
				}

				if (!$numeric_array){
                    if(strlen($key) > 0){
                        $xml_serialized_string .= "\n".str_repeat("\t", $level)."<$key$attributes>";
                    }
					if (is_array($value)){
						$xml_serialized_string .= XML::XML_serialize($value, $header, $level+1);
					}
					else{
						$inline = true;
						$value = XML::internal_htmlspecialchars($value,ENT_NOQUOTES);
						$value = preg_replace("/\r\n/", "\\n", $value);
						$value = preg_replace("/\n/", "\\n", $value);
						$value = preg_replace("/\r/", "\\n", $value);
						$xml_serialized_string .= $value;
					}
                    if(strlen($key) > 0){
                        if(is_array($value) && count($value) == 1 && XML::is_single_enum_obj($value)){
                            $xml_serialized_string .= "</$key>\n";
                        }else{
                            $xml_serialized_string .= ($inline ? "" :str_repeat("\t", $level))."</$key>\n";
                        }
                    }
				}
			}
			else
			{
				if (defined('XML_DEBUG') && constant('XML_DEBUG'))
					echo "Skipping attribute record for key $key<bR>";
			}
		}
	}
    //Removing extra blank lines
    $xml_serialized_string = preg_replace("/\n\n+/s", "\n", $xml_serialized_string);
	if ($level == 0 && $header)
	{
		$xml_serialized_string = sprintf("<?xml version=\"1.0\" encoding=\"utf-8\"%s>\n", "?") . $xml_serialized_string;
		return $xml_serialized_string;
	}
	else
	{
		return $xml_serialized_string;
	}
}

public static function is_single_enum_obj($obj){
    if(!is_array($obj)){
        return TRUE;
    }
    if(array_key_exists(0, $obj)){
        if(is_array($obj[0]) && count($obj[0]) >1){
            return XML::is_single_enum_obj($obj[0]);
        }else{
            return TRUE;
        }
    }
    return FALSE;
}

public static function &XML_format(&$xml_string){
    $tool = new XML_Beautifier();
    $result = &$tool->formatString($xml_string);
    return $result;
}

// serialize XML without line breaks and leading blanks
public static function XML_simple_serialize($data, $header=true, $prior_key=NULL)
{
	# assumes a hash, keys are the variable names
	$xml_serialized_string = "";
	if (is_array($data))
	{
		while (list($key, $value) = each($data))
		{
			$inline = false;
			$numeric_array = false;
			$attributes = "";
			if (defined('XML_DEBUG') && constant('XML_DEBUG'))
				echo "current key: '$key'; prior key: '$prior_key'<br>";

			// if a master key name (not an attribute)
			if (!strstr($key, " attr"))
			{
				// if attributes exist for key
				if (array_key_exists("$key attr", $data))
				{
					while ( list($attr_name, $attr_value) = each($data["$key attr"]))
					{
						if (defined('XML_DEBUG') && constant('XML_DEBUG'))
							echo "Found attribute $attribute_name with value $attribute_value<br>";
						$attr_value = &internal_htmlspecialchars($attr_value, ENT_QUOTES);
						$attributes .= " $attr_name=\"$attr_value\"";
					}
				}

				if (is_numeric($key))
				{
					if (defined('XML_DEBUG') && constant('XML_DEBUG'))
						echo " key is numeric<br>";
					$key = $prior_key;
				}
				else
				{
					if (is_array($value) && array_key_exists(0, $value))
					{
						if (defined('XML_DEBUG') && constant('XML_DEBUG'))
							echo " value looks like numeric array.<br>";
						$numeric_array = true;
						$xml_serialized_string .= XML_simple_serialize($value, 0, $key);
					}
				}

				if (!$numeric_array)
				{
					$xml_serialized_string .= "<$key$attributes>";

					if (is_array($value))
					{
						$xml_serialized_string .= XML_simple_serialize($value, $header);
					}
					else
					{
						$inline = true;
						$xml_serialized_string .= preg_replace("/\n/", "\\n", internal_htmlspecialchars($value));
					}

					$xml_serialized_string .= "</$key>";
				}
			}
			else
			{
				if (defined('XML_DEBUG') && constant('XML_DEBUG'))
					echo "Skipping attribute record for key $key<bR>";
			}
		}
	}
	if ($level == 0 && $header)
	{
		$xml_serialized_string = sprintf("<?xml version=\"1.0\" encoding=\"utf-8\"%s>\n", "?") . $xml_serialized_string;
		return $xml_serialized_string;
	}
	else
	{
		return $xml_serialized_string;
	}
}

/**
 * Unserialze XML to object
 * @param String $xml
 * @param Bool $force_lowercase_tags
 * @param Object $force_node_enum Pass TRUE will return all xml node as array(0=>node_object), pass array('tag01','tag02') will return tag01 and tag02 as array(0=>node_object)
 * @return array
 */
public static function & XML_unserialize(&$xml, $force_lowercase_tags=true,$force_node_enum=false)
{	
	$xml_parser = new XML();
	$xml_parser->tolower = $force_lowercase_tags;
	
	if (false)
	{
		$data =& $xml_parser->parse(_encode_xml($xml), $force_node_enum);
		if ($data === false)
			$GLOBALS['XML_ERROR'] = $xml_parser->get_error_text();
		$xml_parser->destruct();
		
		$msg = var_export($data,true);
		$data = _decode_data($data);
		$msg = var_export($data,true);
	}
	else
	{
		$data =& $xml_parser->parse($xml,$force_node_enum);
		if ($data === false)
			$GLOBALS['XML_ERROR'] = $xml_parser->get_error_text();
		$xml_parser->destruct();	
	}
	return $data;
}
function _encode_xml($xml)
{
	$new_xml = '';
	if(strpos($xml,"?>")){	
		$new_xml = substr($xml,0,strpos($xml,"?>") + 2);
		$xml = substr($xml,strpos($xml,"?>") + 2);
	}
 	$parse_error = true;
	$str_len = strlen($xml);
	$parsing_node = false;
	$parse_node_is_begin = true;
	$str_node = '';
	$str_node_att = '';
	$parsing_node_att = false;
	
	$no_content_node = false;
	for($i=0;$i < $str_len; $i++)
	{
	   $ch = $xml{$i};
	   switch($ch)
	   {
	   	case '<':
	   		//begin parsing node
	   		$parsing_node = true;
	   		
	   		$parse_node_is_begin = true;
	   		$str_node = '';
	   		break;
	   	case '>':
		    $encode_str = '_';
			for($j=0;$j<strlen($str_node);$j++)
			{
				$encode_str = $encode_str.ord($str_node{$j}).'_';
				
			}
			
			if($no_content_node){
				$new_xml .= '<'.$encode_str.' '.$str_node_att.'/>';
			}else{
				if($parse_node_is_begin){
					$new_xml .= '<'.$encode_str.' '.$str_node_att.'>';
				
				}else{
					$new_xml .= '</'.$encode_str.'>';
				}
				
			}
			
			//end parse node
	   		$parsing_node = false;
	   		$parsing_node_att = false;
	   		$str_node_att = '';
	   		
	   		$no_content_node = false;
	   		break;
	   	case '/':
	   		if($str_node != '') $no_content_node = true;
	   		$parse_node_is_begin = false; 		
	   		break;
	   	case ' ':
	   		if($parsing_node && $parse_node_is_begin){
   			    $parsing_node_att = true;
	   		    $str_node_att .= ' ';
	   		}else{
	   			$new_xml .= $ch;
	   		}

	   		break;
	   	default:
	   		if($parsing_node){
	   			if($parsing_node_att){
	   				$str_node_att .= $ch;
	   			}else{
	   				$str_node .= $ch;
	   			}
	   		}else{
	   			$new_xml .= $ch;
	   		}
	   		
	   }	
	}
	if($parse_error){
		return $new_xml;
	}else{
		return $xml;
	}
}
function _decode_data($data)
{
	if(!is_array($data)){return $data;}
	$decode_error = true;
	
	
	$data_ret = array();
	$aKeys = array_keys($data);
	$aVals = array_values($data);
	
	for($x=0;$x<count($aKeys);$x++)
	{

		$key = $aKeys[$x];
		if(strpos($key,"_") === false){
			$key_decode = $aKeys[$x];
		}else{
			$key_decode = '';	
			$key = substr($key,1,strlen($key)-2);
			foreach(split('_',$key) as $ascii_code)
			{
				$key_decode .= chr($ascii_code);
			}			
		}
		if(is_array($aVals[$x]))
		{
			$data_ret[$key_decode] = _decode_data($aVals[$x]);
		}else{
			$data_ret[$key_decode] = $aVals[$x];
		}
	}
	return $data_ret;
	
}

function internal_htmlentities($str, $quoteStyle=ENT_COMPAT, $charset='utf-8'){
        return htmlentities($str, $quoteStyle, $charset);
}
function internal_htmlspecialchars($str, $quoteStyle=ENT_COMPAT, $charset='utf-8'){
        return htmlspecialchars($str,$quoteStyle, $charset);
}
function internal_html_entity_decode($str, $quoteStyle=ENT_COMPAT, $charset='utf-8'){
        return html_entity_decode($str,$quoteStyle, $charset);
}

}
?>
